package com.zyj.core.executor;

import com.zyj.core.cache.CacheKey;
import com.zyj.core.cache.PerpetualCache;
import com.zyj.core.config.Configuration;
import com.zyj.core.config.MappedStatement;
import com.zyj.core.sqlsource.BoundSql;
import com.zyj.core.sqlsource.SqlSource;

import java.util.List;

/**
 * 在该类中完成一级缓存的查询任务，也不是真正查询数据库的执行器，它的三个子类才是真正查询数据库的执行器
 */
// 抽象出来一级缓存的处理逻辑
public abstract class BaseExecutor implements Executor {

    // 一级缓存
    private PerpetualCache cache = new PerpetualCache();

    // 抽象模板方法设计模式
    // 里氏替换设计原则
    // 三个子类没有再重写实现该父类方法，而是利用抽象模板方法设计模式实现了钩子方法
    // 但是三个子类继承了该方法，所以在CachingExecutor中虽然持有的是子类执行器对象，但是调用的时候会走到该方法
    @Override
    public <T> List<T> query(Configuration configuration, MappedStatement mappedStatement, Object param) {

        /**
         * 在此处要查询一级缓存【默认开启】
         * MyBatis中一级缓存是默认开启的，不需要任何配置。
         *
         * 缓存的优先级
         * 通过mybatis发起的查询，作用顺序为：二级缓存->一级缓存->数据库 ，其中任何一个环节查到不为空的数据，都将直接返回结果
         *
         * 一级缓存的作用范围：一级缓存的作用域是SqlSession级别
         * 二级缓存的作用范围：二级缓存的作用域是namespace级别
         */

        // 此处从mappedStatement中，取出来的是RawSqlSource或者是DynamicSqlSource类型
        // 如果此处是RawSqlSource，那么sql语句已经完成了解析，但是依然还没有完成boundSql的封装，不管是哪种类型的SqlSource，都是在调用getBoundSql方法时，才完成的boundSql对象的封装
        SqlSource sqlSource = mappedStatement.getSqlSource();

        // 调用sqlSource中的getBoundSql方法，如果是DynamicSqlSource，此处用getBoundSql方法时才会触发sql的解析工作，得到解析之后的sql并且封装进boundSql对象中
        // 如果是RawSqlSource，在RawSqlSource对象创建时，就已经完成了sql的解析工作
        BoundSql boundSql = sqlSource.getBoundSql(param);

        // 从boundSql取出解析完成的sql，得到缓存中的key，实际源码中cacheKey是一个对象，里面封装了几个部分的数据
        CacheKey cacheKey = crateCacheKey(boundSql);

        // 先从一级缓存中获取数据【在查询一级缓存之前，已经查询了二级缓存，执行到此处意味着二级缓存中没有命中】
        List<T> list = (List<T>) cache.get(cacheKey);

        // 没有再查询数据库
        if (list == null) {
            list = queryFromDataBase(configuration, mappedStatement, boundSql, param);

            /**
             * 从数据库中查询到数据后再放入缓存
             */
            cache.put(cacheKey, list);
        }

        return list;
    }

    /**
     * 之所以要在此处使用抽象模板方法，是为了要在真正查询数据库之后，在BaseExecutor中完成一级缓存的查询任务
     *
     * @param configuration
     * @param mappedStatement
     * @param boundSql
     * @param param
     * @param <T>
     * @return
     */
    // 钩子方法
    protected abstract <T> List<T> queryFromDataBase(Configuration configuration, MappedStatement mappedStatement, BoundSql boundSql, Object param);

    /**
     * 创建一级缓存key
     *
     * @param boundSql
     * @return
     */
    protected CacheKey crateCacheKey(BoundSql boundSql) {
        // 取出boundSql中封装好的sql
        // 此处boundSql中持有的sql为：SELECT * FROM user WHERE sex = ? AND username like '%千年老亚瑟' AND username like '%千年老亚瑟' AND 1=1
//        System.out.println(boundSql.getSql());
        return new CacheKey(boundSql.getSql());
    }
}
